/*
 *   This File is Part of CuteOgre Game Engine
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *	Copyright 2010 Alexandre Pires ( c.alexandre.pires@gmail.com )
 *  Created on: 2010/08/22
 */


#include "Ogg/OggFile.h"
#include "Ogg/DummyStream.h"
#include "Ogg/VorbisStream.h"
#include "Ogg/TheoraStream.h"

#include "OGRE/OgreResourceManager.h"

OggFile::OggFile(const QString& rName, const QString &rGroup) {

	Ogre::ResourceGroupManager* resourceMgr =
			Ogre::ResourceGroupManager::getSingletonPtr();

	mInputStream = resourceMgr->openResource(rName.toStdString(),
			rGroup.toStdString());

	int ret = ogg_sync_init(&mState);

	mValid = (ret == 0);

	if (mValid) {
		mUnloaded = false;
		initStreams();
		if (!mVorbisStream.isNull())
			mVorbisStream->processHeaders();
		if (!mTheoraStream.isNull())
			mTheoraStream->processHeaders();
	}
}

OggFile::~OggFile() {

	if (!mUnloaded)
		unload();

	if (mStreams.size() > 0) {

		foreach ( IOggStreamPtr header, mStreams )
			{
				header.clear();
			}

		mStreams.clear();
	}

	mInputStream->close();
	mInputStream.setNull();
}

void OggFile::unload() {

	if (mStreams.size() > 0) {

		foreach ( IOggStreamPtr header, mStreams )
			{
				header->lockState();
				header->disable();
				header->unlockState();
			}
	}

	int ret = ogg_sync_clear(&mState);
	assert( ret == 0 );

	mUnloaded = true;
}

void OggFile::initStreams() {

	while (1) {

		ogg_page page;

		getPage(&page);

		int serial = ogg_page_serialno(&page);

		IOggStreamPtr stream;

		if (ogg_page_bos(&page)) {

			if (VorbisStream::checkHeaderSignature(&page)) {

				stream = IOggStreamPtr(new VorbisStream(serial, this));
				if (mVorbisStream.isNull())
					mVorbisStream = stream.staticCast<VorbisStream> ();

			} else if (TheoraStream::checkHeaderSignature(&page)) {

				stream = IOggStreamPtr(new TheoraStream(serial, this));
				if (mTheoraStream.isNull()) {
					mTheoraStream = stream.staticCast<TheoraStream> ();
				}
			} else {

				stream = IOggStreamPtr(new DummyStream(serial));
			}

			mStreams[serial] = stream;

			if (stream->isActive()) {
				int ret = ogg_stream_pagein(stream->getState(), &page);
				assert(ret == 0);
			}
			continue;
		}

		assert ( mStreams.find(serial) != mStreams.end() );
		stream = mStreams[serial];

		if (stream->isActive())
			ogg_stream_pagein(stream->getState(), &page);

		break;
	}
}

bool OggFile::getPage(ogg_page* rPage) {

	int ret = 0;

	// If we've hit end of file we still need to continue processing
	// any remaining pages that we've got buffered.
	mMutex.lock();

	if (mInputStream->eof()) {

		ret = (ogg_sync_pageout(&mState, rPage) == 1);
		mMutex.unlock();

		return ret;
	}

	while ((ret = ogg_sync_pageout(&mState, rPage)) != 1) {

		// Returns a buffer that can be written too
		// with the given size. This buffer is stored
		// in the ogg synchronisation structure.
		char* buffer = ogg_sync_buffer(&mState, 8192);
		assert ( buffer );

		// Read from the file into the buffer
		int bytes = mInputStream->read(buffer, 8192);

		if (bytes == 0) {
			// End of file.
			continue;
		}

		// Update the synchronisation layer with the number
		// of bytes written to the buffer
		ret = ogg_sync_wrote(&mState, bytes);
		assert ( ret == 0 );
	}
	mMutex.unlock();

	return true;
}

bool OggFile::getPacket(int rSerial, ogg_packet* rPacket) {

	if (mStreams.find(rSerial) == mStreams.end())
		return false;

	IOggStreamPtr stream = mStreams[rSerial];

	stream->lockState();
	int ret = ogg_stream_packetout(stream->getState(), rPacket);
	stream->unlockState();

	if (ret == -1) {

		return getPacket(rSerial, rPacket);

	} else if (ret == 0) {

		ogg_page page;

		if (!getPage(&page)) {
			return false;
		}

		mMutex.lock();

		int serial = ogg_page_serialno(&page);
		assert ( mStreams.find(serial) != mStreams.end() );

		IOggStreamPtr pageStream = mStreams[serial];

		if (pageStream->isActive()) {

			pageStream->lockState();

			ret = ogg_stream_pagein(pageStream->getState(), &page);
			assert ( ret == 0 );

			pageStream->unlockState();
		}

		mMutex.unlock();

		return getPacket(rSerial, rPacket);
	}

	return true;
}

bool OggFile::peekPacket(int rSerial, ogg_packet* rPacket) {

	if (mStreams.find(rSerial) == mStreams.end())
		return false;

	IOggStreamPtr stream = mStreams[rSerial];

	stream->lockState();
	int ret = ogg_stream_packetpeek(stream->getState(), rPacket);
	stream->unlockState();

	ogg_page page;

	if (ret == -1) {

		return peekPacket(rSerial, rPacket);

	} else if (ret == 0) {

		if (!getPage(&page)) {
			return false;
		}

		mMutex.lock();

		int serial = ogg_page_serialno(&page);
		assert ( mStreams.find(serial) != mStreams.end() );

		IOggStreamPtr pageStream = mStreams[serial];

		if (pageStream->isActive()) {

			pageStream->lockState();

			ret = ogg_stream_pagein(pageStream->getState(), &page);
			assert ( ret == 0 );

			pageStream->unlockState();
		}

		mMutex.unlock();

		return peekPacket(rSerial, rPacket);
	}

	return true;
}
